Skip to content

feat(messaging): store a push device's origin at registration and carry it in every push to the device - #30705

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-9eb7x4
Nov 6, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-9eb7x4

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

domain:server#3 · os-elon-musk · session_016WV4czbHcjvoBYWDw2gRAq

Fixes #29768

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:手机注册推送设备时带上的 origin(控制台加载自哪台服务器)现在会存到 sys_user_device 上,listDevices 读得回来,每条推送的 data 里都带着它。
  • 为什么改:裁决 B([Decision] Push delivery data: name the server that sent a push, so a mobile shell with several saved servers opens a tap on the right one #29642)的服务端一半。保存了两台服务器的手机壳,点开 B 服务器发来的推送会落到 A 上;有了 origin,壳就能按它打开正确的服务器。
  • 风险与回滚:新增一个可空列,不带 origin 的注册与推送完全照旧;spec 只改描述和注释,不改校验。回滚即 revert。
  • 先看:packages/services/service-messaging/src/device-registry.ts 的写行和 toUserDevice;推送通道的复制在 main 上已存在(push-channel.ts 的 withOrigin),本 PR 未改它。
  • 两分钟验证:pnpm --filter @objectstack/service-messaging exec vitest run src/device-registry.integration.test.ts,预期 14 passed。
  • 需要你做的:无;席位验收、CI 全绿后落地。

What changes

@objectstack/service-messaging.

  • sys_user_device gains an optional origin text column, deliberately without maxLength: spec's origin pattern bounds the shape, not the length, so a bounded column could refuse a value the registration accepts.
  • DeviceRegistry writes origin: request.origin ?? null beside app_id / locale on both the insert and the refresh (a refresh replaces it, as it does those two), and toUserDevice sets device.origin when the row holds one.
  • The push channel already copied device.origin into each device's push data (withOrigin in push-channel.ts, landed with the channel); until now the registry never stored one, so nothing reached it. No change there.
  • The field's label and help are translated in all ten shipped translated locales, ru, de and ko included since main added them (check-i18n-bundles.mjs --write, then hand translation, then --write again; the en bundle is regenerated from the source).

@objectstack/spec, description and docblock only.

  • RegisterDeviceRequestSchema.origin and UserDeviceSchema.origin drop their [PLANNED — not yet stored] prefix; the marker pin in push-server-origin.test.ts is flipped to assert that no origin member is marked PLANNED.
  • push-server-origin.ts docblock takes the three should-fix items of contract review 6595162668 by the docblock route: it names the DNS-label limit (_ and a trailing-dot host are refused although window.location.origin can answer them), the port and IPv6 laxness with the Console as the one producer, and that a dotted IPv4 host is accepted. ⛔ The validator is unchanged.
  • The RegisterDeviceRequestSchema docblock's refresh list now names every request member a refresh rewrites: token, vendor, platform, app, locale, origin and name.
  • Regenerated: content/docs/references/api/protocol.mdx, content/docs/references/system/push-device.mdx (gen:schema && gen:docs).

@objectstack/service-push, docblock only. The PushMessage docblock in push-service.ts names origin as the third delivery-data member, present when the device registered one; the changeset carries '@objectstack/service-push': patch for it.

Not changed, verified: packages/runtime/src/domains/notifications.ts already hands RegisterDeviceRequestSchema.safeParse(body).data to registerDevice, so origin reaches the registry. Both transports in service-push spread message.data unchanged (FCM into message.data, APNs at the payload top level).

Why

Ruling B on #29642 (record 6593872191, maintainer 「同意」): "the Console sends the origin it was loaded from in RegisterDeviceRequest. The server stores it on the UserDevice row, and the push channel copies it into PushDeliveryDataSchema." The spec half (PR #29767) is merged; this is the server half. The Console producer stays with #29042.

Verification

Measured on origin/main b77c160a (registry and object reverted to main, the new pins kept): a registration with origin: 'https://crm.example.com' stored a row whose origin is undefined, listDevices answered no origin, and a push through the real channel over the real registry carried { notificationId, route } only.

  • src/device-registry.integration.test.ts, new top-level describe (4, real ObjectQL over SqlDriver): the registration's origin is stored and listDevices answers it; a registration without one stores null and the device has no origin member; a refresh replaces the stored origin and a refresh without one clears it; a push through createPushChannel with the MessagingService as its device registry carries the stored origin for that device and none for a device without one, and every data object parses as PushDeliveryDataSchema.
  • src/objects/user-device.object.test.ts (+1): the origin column is optional text with no maxLength.
  • packages/spec/src/system/push-server-origin.test.ts: the marker pin flipped.
  • Ablations: registry and object at main ⇒ 4 red (the null/absent case is the negative control and stays green); only the read-back line removed ⇒ 2 red (list and push), the row pin green; the PLANNED prefix put back on UserDeviceSchema.origin ⇒ the flipped pin red. All green at head.
  • @objectstack/service-messaging: 66 files / 850 tests green at 48036dd1, 67 files / 858 tests green on the merged tree at 6dc84f5e; typecheck green. check-i18n-bundles.mjs: in sync; check:i18n-coverage: none new. @objectstack/spec check:generated: every artifact current (check:test-typecheck needs an 8 GB heap in this container; green with it).
  • Every dispatch-gates --commands family at 48036dd1: 108 derived, 108 run, all exit 0, 0 NOT-MEASURED (--ran reconcile). pnpm lint: exit 0. The same sweep on the final head 6dc84f5e is reported on the card.

Skills-docs: git grep -n -i -E "registerDevice|notifications/devices|RegisterDeviceRequest|PushDeliveryData|push.*origin" -- skills content/docs read: no skills/** hit; content/docs/kernel/runtime-services/push-service.mdx already lists origin among the registration members and says nothing about it being unstored, still true; release notes untouched. Generated content/docs/references/** regenerated, not hand-edited.

Contract review 6672159811 (PASS on 48036dd1) left two should-fix docblock items, the PushMessage data list and the refresh list; both are folded in at 7e4b969d, delta review 6672299353 PASS. ccea409a merges main, whose new ru, de and ko service-messaging bundles lacked the new key (CI check:i18n / check:i18n-coverage on the merge ref), and 6dc84f5e translates it in those three.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic.
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: its generated artifacts are regenerated and committed.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English (the 维护者速读 block is the lane's maintainer summary).

…ery push

sys_user_device gains an optional, unbounded origin column. The device
registry stores the registration's origin on the row (null without one,
replaced by a refresh like app_id and locale) and listDevices answers it,
so the push channel's existing copy of device.origin into the push data
finally has a value to copy.

Spec retires the two [PLANNED — not yet stored] prefixes on
RegisterDeviceRequestSchema.origin and UserDeviceSchema.origin, flips the
marker pin, and the push server origin docblock names where its pattern
is knowingly narrower (underscore labels, trailing-dot hosts) and looser
(port digits, the IPv6 arm) than a URL origin, and that IPv4 is accepted.

Claude-Session: https://claude.ai/code/session_016WV4czbHcjvoBYWDw2gRAq
Co-authored-by: Claude <noreply@anthropic.com>
…shipped language

Claude-Session: https://claude.ai/code/session_016WV4czbHcjvoBYWDw2gRAq
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation protocol:system tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 5, 2026
@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/service-messaging, @objectstack/service-push, @objectstack/spec, touching 6 documentable anchor(s). ⚠️ 2 changed file(s) yielded no anchor (packages/services/service-push/src/push-service.ts, packages/spec/src/system/push-server-origin.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/kernel/runtime-services/push-service.mdx (via RegisterDeviceRequestSchema (symbol, a top-level const), sys_user_device (symbol, a field of const object arObjects; a field of const object deObjects; a field of const object enObjects; a field of const object esESObjects; a field of const object frObjects; a field of const object jaJPObjects; a field of const object koObjects; a field of const object ptBRObjects; a field of const object ruObjects; a field of const object zhCNObjects; a field of const object zhHantObjects))
  • content/docs/plugins/packages.mdx (via sys_user_device (symbol, a field of const object arObjects; a field of const object deObjects; a field of const object enObjects; a field of const object esESObjects; a field of const object frObjects; a field of const object jaJPObjects; a field of const object koObjects; a field of const object ptBRObjects; a field of const object ruObjects; a field of const object zhCNObjects; a field of const object zhHantObjects))

⛔ 1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v18/18-3.mdx (via UserDevice (symbol, a top-level const object), sys_user_device (symbol, a field of const object arObjects; a field of const object deObjects; a field of const object enObjects; a field of const object esESObjects; a field of const object frObjects; a field of const object jaJPObjects; a field of const object koObjects; a field of const object ptBRObjects; a field of const object ruObjects; a field of const object zhCNObjects; a field of const object zhHantObjects))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/services/service-push/src/push-service.ts, packages/spec/src/system/push-server-origin.ts) — pages documenting those are invisible to this run
  • the SDK route bridge reached 63 of 221 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 103 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 171 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 4564d6fa5b79764676d554de1cedd5946ca0f120 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 1a3c380470eb00a7f12d78da4ea29fe3b43b212e — the merge of head 6dc84f5e922e380bc96cf43431a4c3ab950e60e4 into base 4564d6fa5b79764676d554de1cedd5946ca0f120, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 1a3c380470eb00a7f12d78da4ea29fe3b43b212e && git checkout 1a3c380470eb00a7f12d78da4ea29fe3b43b212e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 4564d6fa5b79764676d554de1cedd5946ca0f120 6dc84f5e922e380bc96cf43431a4c3ab950e60e4 && git checkout -B drift-repro 4564d6fa5b79764676d554de1cedd5946ca0f120 && git merge --no-ff 6dc84f5e922e380bc96cf43431a4c3ab950e60e4

node scripts/docs-audit/affected-docs.mjs --json 4564d6fa5b79764676d554de1cedd5946ca0f120

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 4564d6fa5b79764676d554de1cedd5946ca0f120 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-elon-musk · session_016WV4cytQjG9X1CrCgytUM5

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 48036dd19332b8cf1856fd162bb61996411d738f
Local-runs: none
Scope: packages/spec/src/api/protocol.zod.ts, packages/spec/src/system/push-device.zod.ts, packages/spec/src/system/push-server-origin.ts + packages/spec/src/system/push-server-origin.test.ts, packages/services/service-messaging/src/device-registry.ts, packages/services/service-messaging/src/objects/user-device.object.ts, packages/services/service-messaging/src/push-channel.ts (withOrigin, unchanged on main), packages/services/service-messaging/src/device-registry.integration.test.ts, packages/services/service-messaging/src/index.ts, packages/services/service-push/src/push-service.ts (data forwarding), packages/runtime/src/domains/notifications.ts (as the PR cites it), content/docs/protocol/objectql/types.mdx (the text column mapping), .changeset/29768-push-device-origin-stored.md

Inputs read: card #29768 body and all 6 comments (ruling B on #29642 as quoted in the body; the three should-fix items of 6595162668; claims 6670490478 / 6671550587; os-dev-report 6671567236); PR #30705 body, 19-file list, net diff vs main (merge-base b77c160a, 2 ahead / 13 behind); the docs-drift comment 6671569480; the 65 check-runs on this head.

① Derived judgments

  • Retiring [PLANNED — not yet stored] is now TRUE. On this head the chain is closed end to end: registerOnce builds one values object used by both the refresh update and the first insert, and it now carries origin: request.origin ?? null; toUserDevice sets device.origin when the row holds one and omits the member on null; list() is listDevices; createPushChannel.send calls opts.devices.listDevices(recipient) and passes withOrigin(data, device.origin) to push.send; withOrigin on main copies a value that PushDeliveryDataSchema.shape.origin accepts; both transports forward data whole (push-service.ts: FCM message.data, APNs top level). The runtime route already hands RegisterDeviceRequestSchema.safeParse(body).data to registerDevice, so origin reaches the registry. The new integration describe pins the chain with the real ObjectQL/SqlDriver registry and the real channel, and the flipped spec pin asserts no PLANNED on all three members. Declaration now equals enforcement; retiring the markers (and regenerating the two mdx rows) is correct.
  • Accept-set unchanged. PUSH_SERVER_ORIGIN_PATTERN, PUSH_SERVER_ORIGIN_MESSAGE, optionality and the three schemas' structure are identical to main; only .describe() text and the docblock move, so the published JSON Schema pattern is the same (pinned by "publishes the same pattern in each JSON Schema"). No new export: push-server-origin.ts stays barrel-less. Clause-②: no is right.
  • Docblock statements checked against the regex ^https?:\/\/(?:L(?:\.L)*|\[[0-9a-f:.]+\])(?::[0-9]{1,5})?$ with L = [a-z0-9](?:[a-z0-9-]*[a-z0-9])?: (a) underscore refused — _ is in no character class, so http://my_svc:3000 fails; TRUE. (b) trailing dot refused — each \. must be followed by a label, so https://crm.example.com. cannot reach $; TRUE. (c) a dotted IPv4 host is admitted by the DNS arm — all-digit labels satisfy L; TRUE (the test already round-trips http://192.168.1.20:3000). (d) port is one to five digits, so :443, :80, a leading zero and 65536–99999 are admitted and nothing range-checks; TRUE (six digits are refused, which the docblock does not claim otherwise). (e) the bracketed arm admits any run of hex digits, colons and dots, [:::] included; TRUE, and lowercase hex only, consistent with the "lowercase host" sentence and with browser serialisation. (f) "a registration naming one is refused as a whole" — a Zod object parse fails on the member, so the route answers 400; TRUE. No statement in the docblock is false against the pattern.
  • Column. Field.text with no maxLength and no index keying it maps to TEXT on every SQL dialect (types.mdx, text family), so the "unbounded" reasoning holds and the column cannot refuse a value the pattern accepts (a 253-character DNS name plus scheme and port exceeds 255). Existing deployments get the column through the additive sync (ALTER TABLE ADD COLUMN). UserDevice is exported from the package index, so the object definition's fields gains a key; the spec's UserDeviceSchema already declared origin, so the published contract is unchanged and the implementation catches up to it.
  • Refresh semantics. A refresh without origin stores null and clears the old value, the same overwrite app_id and locale already have; the RegisterDeviceRequestSchema docblock now names origin in the refresh list. Consistent and pinned.

② Semver level

Changeset: @objectstack/service-messaging: patch, @objectstack/spec: patch, body carrying Clause-②: no and Metadata-protocol: none, matching the PR body. Right by AGENTS.md's rule: the level follows the metadata-protocol kind alone, none takes patch, and a non-protocol widening ships patch; the feat prefix does not raise it. Metadata-protocol: none is right: sys_user_device is an engine-owned system object, not a key a metadata author writes, so no authorable-surface ledger moves (check:authorable-surface reads the spec's metadata schemas via gen:schema, which this diff does not touch; Spec property liveness is green). The ledgers that do move are the object's eight translation bundles, regenerated and committed. The spec patch is owed because .describe() text ships in the published JSON Schema and docs.

Check-runs on 48036dd1: 65 runs read, every one completed with success or skipped at read time, none in progress; Spec property liveness, Check Changeset, Check Documentation Links, Flag docs affected by code changes, Build Docs, Test Core (1–8/8), Governed Surface Queue Guard all success.

③ Boundary flags

  • open_questions: none. The dev's out-of-scope note (the PushMessage docblock in service-push lists notificationId and route only) is taken up below as a should-fix.
  • The three should-fix items of 6595162668 are answered by the docblock route, which that record allowed ("or name the limit in the docblock" / "or note that the Console is the one producer" / "should say an IPv4 host is accepted too"); the validator is untouched, as the card's claim required.
  • Docs drift (6671569480): content/docs/kernel/runtime-services/push-service.mdx lists origin among the registration members and says nothing about it being unstored, so it stays true; the release page is read-only and untouched.
  • Author is objectstack-fleet[bot]; no out-of-relay login. The PR is a draft.
  • Independence: rendered by an isolated contract-review subagent fed the card, the PR, the diff, the cited code on main and the check-runs, not the seat's conclusions; adopted by the seat named in Reviewed-by:.

④ Design judgment

  • protocol.zod.ts — the description now states what the server does, and the refresh list names origin; holds against registerOnce.
  • push-device.zod.ts — "Absent on a device registered without one" holds against toUserDevice (member omitted on null) and is pinned.
  • push-server-origin.ts — the producer-bounded design is coherent: the shell compares the carried origin byte for byte with a saved server's, so an admitted non-canonical value can only fail to match, never name the wrong server; the narrower DNS-label arm costs a push its origin for _ or trailing-dot hosts, and the docblock says so and tells the producer to omit the member. Against the ruling (store at registration, copy into every push) and the first consumer (withOrigin, then the mobile shell), the change holds.

Findings (none must-fix):

  • should-fix — packages/services/service-push/src/push-service.ts PushMessage docblock describes the data as the notification id and route only; origin now rides there on every push to a device that registered one. A one-line addition, or a follow-up card beside Console: register the mobile shell's native push token after sign-in, unregister it on sign-out #29042 so the Console producer and the transport docblock move together.
  • should-fix — RegisterDeviceRequestSchema docblock: "refreshes that device (its token, vendor, app, locale and origin)". The refresh values also replace platform and name; the omission predates this PR but the hunk rewrote the sentence, so finish it.

Implemented-by: claude/project-thread-9eb7x4
Reviewed-by: session_016WV4cytQjG9X1CrCgytUM5

VERDICT: PASS

…ish the refresh list

Claude-Session: https://claude.ai/code/session_016WV4czbHcjvoBYWDw2gRAq
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-elon-musk · session_016WV4cytQjG9X1CrCgytUM5

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 7e4b969d9d223c01773a445e702dda9740a8ee0d
Local-runs: none
Scope: packages/spec/src/api/protocol.zod.ts + packages/services/service-push/src/push-service.ts, .changeset/29768-push-device-origin-stored.md, packages/services/service-messaging/src/device-registry.ts (the refresh values on main), packages/spec/scripts/build-docs.ts (what gen:docs projects), content/docs/references/api/protocol.mdx (on this head)

Delta review of record 6672159811 (PASS on 48036dd1): one commit, 7e4b969d "docs(push,spec): name origin in the PushMessage data docblock and finish the refresh list", 3 files, +11/-6, folding in that record's two should-fix items. The rest of the tree is unchanged from the reviewed head and keeps that record's judgments; nothing below re-opens them.

① Derived judgments

  • Accept-set / public surface: unchanged. The delta is two docblocks and one changeset line. No .describe(), pattern, member, export or route moves; RegisterDeviceRequestSchema and PushMessage parse and type exactly as before. Clause-②: no / Metadata-protocol: none stand.
  • Refresh list is now complete and true. The docblock names "token, vendor, platform, app, locale, origin and name"; the one values object registerOnce writes on both the refresh update and the first insert carries exactly those request members (token, vendor, platform, app_id, locale, origin, name) plus the server's own state (enabled, invalidated_at, last_seen_at), which are not request members and belong in no refresh list. Should-fix 2 of 6672159811 is closed.
  • PushMessage docblock is now true. It names origin as a third data member, conditional on the device having registered one, and keeps the FCM message.data / APNs top-level placement sentence, which withOrigin and the transports' whole-data forwarding make accurate. Should-fix 1 of 6672159811 is closed.
  • No generated artifact is owed by the delta. build-docs.ts projects the module doc block and the schemas' .describe() text; the per-schema TSDoc that moved is not projected (the "refreshes that device" row in protocol.mdx on this head is the deviceId member's own description, unchanged). So the two reference pages stay current without a regeneration, and check:docs has nothing to flag from this commit.

② Semver level

Changeset adds '@objectstack/service-push': patch with a bullet saying docblock only. Right: @objectstack/service-push is a released package (18.2.0, not private), the TSDoc ships in its .d.ts, and a Metadata-protocol: none change takes patch. The two existing patch lines and both declaration lines are unchanged. Check Changeset on 7e4b969d: completed · success.

Check-runs on 7e4b969d at read time (2026-11-05T23:28Z): 42 runs; completed · success: filter, Spec property liveness, Plan Test Core shards, PR-body guards, Lint · PM dispatch-gates, Governed Surface Queue Guard, Flag docs affected by code changes, Dogfood Regression Gate, Check PR Size, Check Documentation Links, Check Changeset, Auto Label; completed · skipped: the usual opt-in and UI-artifact rows; in_progress: Type Check (4 jobs), Test Core (8/8 shards), Lint (7 jobs), Build Core, Build Docs, Build UI Deps, Live E2E UI. None red when read. The delta is comment text and a changeset line, and the same families were green on 48036dd1; the landing pre-check still reads them green on this head before queueing, as it must.

③ Boundary flags

  • Dev flags / open_questions: none new; the commit is the dev card thread's own (session_016WV4czbHcjvoBYWDw2gRAq).
  • Author is objectstack-fleet[bot]; no out-of-relay login. The PR is still a draft.
  • Independence: rendered by the same isolated contract-review subagent that rendered 6672159811, fed only the delta diff, the new head's check-runs and the cited code; adopted by the seat named in Reviewed-by:.

④ Design judgment

Both hits files hold against the code they describe: the refresh list against registerOnce.values, the data docblock against withOrigin and the transports. No design moved.

Findings (none must-fix):

  • should-fix (prose only) — the PR body is now stale in two places: "Out of the file surface, left as is: the PushMessage docblock … does not name origin" and "The RegisterDeviceRequestSchema docblock's refresh list now names origin too" describe the 48036dd1 tree. Update both lines to the 7e4b969d tree (the service-push docblock is in the diff, the refresh list is complete) so the body matches what lands.

Implemented-by: claude/project-thread-9eb7x4
Reviewed-by: session_016WV4cytQjG9X1CrCgytUM5

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-elon-musk · session_016WV4cytQjG9X1CrCgytUM5

ACCEPT · #29768 · 2026-11-06T00:27Z · Head-sha: 6dc84f5e922e380bc96cf43431a4c3ab950e60e4

席位核对

  1. Shape: 23 files, +190/−22: sys_user_device gains origin (Field.text) beside app_id / locale; registerOnce writes origin: request.origin ?? null on both insert and refresh; toUserDevice reads it back (member omitted on null); the push channel's existing withOrigin now receives the stored value; the two [PLANNED — not yet stored] markers and their pin retire; docblocks on push-server-origin.ts, RegisterDeviceRequestSchema and PushMessage updated; eight translation bundles carry the field. Diff read at this head.
  2. Premise, measured: contract review 6672159811 (PASS, CONTRACT_REVIEW_TIER, head 48036dd1) traced registration → row → channel → both transports; delta review 6672299353 (PASS, head 7e4b969d) covers the docblock-only commit. The seat read the last delta itself: ccea409a is a plain merge of origin/main, and 6dc84f5e touches exactly service-messaging/src/translations/{de,ko,ru}.objects.generated.ts, adding sys_user_device.origin label + help in each, matching the zh-CN entry's meaning. That commit was owed because i18n(service-messaging): ship the notification objects and digest heading in ru, de and ko (第 5 包的 B 半) #30673 added those three bundles on main after this branch's base.
  3. Gates: 108 derived, 108 exit 0 at this head (--ran reconciled, 0 NOT MEASURED); pnpm lint exit 0; check:i18n in sync and check:i18n-coverage none new on the merged tree. Check-runs on this head: 59 success, 10 skipped, 0 failed.
  4. Accepted deviations / corrections: the PR body was patched to this head (the two stale lines the delta review named are gone). The three hand translations were reviewed by the seat, not by the contract reviewer; they are labels and help text only.
  5. Out of scope: none. Skills-docs: holds as written (no skills/** hit; push-service.mdx stays true).
  6. Landing: check-governed-merges --pr 30705: NOT governed, 212 lines. Ordinary queue landing by this seat now: ready + auto-merge.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 6, 2026 00:28
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 6, 2026
Merged via the queue into main with commit e8dc483 Nov 6, 2026
70 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-9eb7x4 branch November 6, 2026 00:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:system size/m tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

notifications: store the push device's origin on sys_user_device and copy it into push data (ruling B on #29642, server half)

1 participant